#ifndef   M_Code_THREADPOOL_H
#define   M_Code_THREADPOOL_H

#include <wx/thread.h>
#include <wx/event.h>
#include <vector>
#include <list>
#include "M_Code_threadedtask.h"
#include "prep.h"

class M_Code_ThreadPool {
  public:
    M_Code_ThreadPool( wxEvtHandler *owner, int id = -1, int concurrentThreads = -1, unsigned int stackSize = 0 );
    ~M_Code_ThreadPool();
    void SetConcurrentThreads( int concurrentThreads );
    int GetConcurrentThreads() const;
    int GetId() const { return m_ID; }
    void AddTask( cbThreadedTask *task, bool autodelete = true );
    void AbortAllTasks();
    bool Done() const;
    void BatchBegin();
    void BatchEnd();
  private:
    template <typename T>
    class CountedPtr {
      private:
        T *ptr;
        long *count;
      public:
        explicit CountedPtr( T *p = nullptr ) : ptr( p ), count( new long( 1 ) ) {}
        CountedPtr( const CountedPtr<T> &p ) : ptr( p.ptr ), count( p.count ) { ++*count; }
        ~CountedPtr() { dispose(); }
        CountedPtr<T> &operator = ( const CountedPtr<T> &p ) {
          if( this != &p ) {
            dispose();
            ptr = p.ptr;
            count = p.count;
            ++*count;
          }
          return *this;
        }
        T &operator * () const { return *ptr; }
        T *operator -> () const { return ptr; }
      private:
        void dispose() {
          if( --*count == 0 ) {
            delete count;
            delete ptr;
          }
        }
    };
    class cbWorkerThread : public wxThread {
      public:
        cbWorkerThread( M_Code_ThreadPool *pool, CountedPtr<wxSemaphore> &semaphore );
        ExitCode Entry();
        void Abort();
        bool Aborted() const;
        void AbortTask();
      private:
        bool m_abort;
        M_Code_ThreadPool *m_pPool;
        CountedPtr<wxSemaphore> m_semaphore;
        cbThreadedTask *m_pTask;
        wxMutex m_taskMutex;
    };

    typedef std::vector<cbWorkerThread *> WorkerThreadsArray;
    struct cbThreadedTaskElement {
      cbThreadedTaskElement( cbThreadedTask *_task = nullptr, bool _autodelete = false )
        : task( _task ),
          autodelete( _autodelete ) {
      }
      void Delete() {
        if( autodelete ) {
          delete task;
          task = nullptr;
        }
      }
      cbThreadedTask *task;
      bool autodelete;
    };
    typedef std::list<cbThreadedTaskElement> TasksQueue;
    wxEvtHandler *m_pOwner;
    int m_ID;
    bool m_batching;
    int m_concurrentThreads;
    unsigned int m_stackSize;
    int m_concurrentThreadsSchedule;
    WorkerThreadsArray m_threads;
    TasksQueue m_tasksQueue;
    bool m_taskAdded;
    int m_workingThreads;
    mutable wxMutex m_Mutex;
    CountedPtr<wxSemaphore> m_semaphore;
    void _SetConcurrentThreads( int concurrentThreads );
    void Broadcast();
    void AwakeNeeded();
  protected:
    friend class cbWorkerThread;
    cbThreadedTaskElement GetNextTask();
    void WorkingThread();
    bool WaitingThread();
    void TaskDone( cbWorkerThread *thread );
};

inline M_Code_ThreadPool::M_Code_ThreadPool( wxEvtHandler *owner, int id, int concurrentThreads, unsigned int stackSize )
  : m_pOwner( owner ),
    m_ID( id ),
    m_batching( false ),
    m_concurrentThreads( -1 ),
    m_stackSize( stackSize ),
    m_concurrentThreadsSchedule( 0 ),
    m_taskAdded( false ),
    m_workingThreads( 0 ),
    m_semaphore( new wxSemaphore ) {
  SetConcurrentThreads( concurrentThreads );
}

inline int M_Code_ThreadPool::GetConcurrentThreads() const {
  wxMutexLocker lock( m_Mutex );
  return m_concurrentThreadsSchedule ? m_concurrentThreadsSchedule : m_concurrentThreads;
}

inline bool M_Code_ThreadPool::Done() const {
  wxMutexLocker lock( m_Mutex );
  return m_workingThreads == 0;
}

inline void M_Code_ThreadPool::BatchBegin() {
  wxMutexLocker lock( m_Mutex );
  m_batching = true;
}

inline void M_Code_ThreadPool::Broadcast() {
  if( m_concurrentThreads == -1 ) {
    return;
  }
  for( std::size_t i = 0; i < static_cast<std::size_t>( m_concurrentThreads - m_workingThreads ); ++i ) {
    m_semaphore->Post();
  }
}

inline void M_Code_ThreadPool::AwakeNeeded() {
  if( m_concurrentThreads == -1 ) {
    return;
  }
  std::size_t awakeThreadNumber = std::min<std::size_t>( m_tasksQueue.size(), ( m_concurrentThreads - m_workingThreads ) );
  for( std::size_t i = 0; i < awakeThreadNumber; ++i ) {
    m_semaphore->Post();
  }
}

#endif
